package roborally.model;

import be.kuleuven.cs.som.annotate.*;
import be.kuleuven.cs.som.taglet.*;
import exception.IllegalPositionException;

/**
 * A super class for all objects that can be placed on Boards
 * 
 * @author Mathieu REYMOND
 * @author Joren VERSPEURT
 * 
 * @version 2.0
 *
 *@invar ...
 *		 | this.hasProperBoardPosition()
 */
public abstract class Element extends Terminatable {
	
	/**
	 * Create a new element with no board and no position.
	 * 
	 * @effect ...
	 * 		   | this(null, null)
	 */
	protected Element() {
		
		this(null, null);
	}
	
	/**
	 * Create a new element with a given board and a given position
	 * 
	 * @param board The given board
	 * @param position The given position
	 * 
	 * @throws IllegalArgumentException
	 * 
	 * @effect ...
	 * 		   | this.setBoardPosition(board, position)
	 */
	protected Element(Board board, Position position) throws IllegalArgumentException {
		super();
		setBoardPosition(board, position);
	}
	
	/**
	 * Check if this element can share a position with element other
	 * 
	 * @param other The other element to check
	 */
	abstract public boolean canSharePositionWith(Element other);
	
	/**
	 * Clone this element.
	 * 
	 * @return ...
	 * 		   | (result.getBoard() == null && result.getPosition() == null) || (result.getBoard() == this.getBoard() && result.getPosition() == this.getPosition())
	 */
	abstract public Element clone();
	
	/**
	 * Gives the element's current board
	 * 
	 */
	@Basic
	public Board getBoard() {
		
		return board;
	}
	
	/**
	 * Gives the element's current position
	 * 
	 */
	@Basic
	public Position getPosition() {
		Position result = position == null ? null : new Position(position.getX(), position.getY());
		
		return result;
	}
	
	/**
	 * Terminate this element
	 * 
	 * @effect ...
	 * 		   | setBoardPosition(null, null)
	 */
	@Override
	public void terminate() {
		
		this.setBoardPosition(null, null);
		super.terminate();
		
	}
	
	/**
	 * Set this element's board and position to the given board and position
	 * 
	 * @param board The given board
	 * @param position The given position
	 * 
	 * @throws IllegalArgumentException
	 * 		   | (board != null && position == null) || (board == null && position != null)
	 * 		   | for any element in board.getElements(position)
	 * 		   |	!this.canSharePositionWith(element)
	 * 
	 * @throws IllegalPositionException
	 * 		   | !board.isValidPosition(position)
	 * 
	 * @pre If the Element is an Item it is not allowed to place the Item on a board if it's already owned by a robot.
	 * 		| for each Board board
	 * 		| 	for each robot in board.getElements(Robot.class)
	 * 		| 		!robot.getItems().contains(this)
	 * 
	 * @post ...
	 * 		 | (new this).getBoard() == board && (new this).getPosition() == position
	 * 
	 * @effect ...
	 * 		 | board.putElement(this, position)
	 */
	public void setBoardPosition(Board board, Position position) throws IllegalArgumentException, IllegalPositionException {
		
		if( (board != null && position == null) || (board == null && position != null) ) throw new IllegalArgumentException();
		Board oldBoard = this.getBoard();
		Position oldPosition = this.getPosition();
		
		if(this.getBoard() != null && getBoard().getElements(this.getClass()).contains(this)) {
			this.getBoard().removeElement(this);
		}
		if(board != null) { //&& !board.getElements(Element.class).contains(this)
			
			for(Element element : board.getElements(position)) {
				if(!this.canSharePositionWith(element)) {
					setBoardPosition(oldBoard, oldPosition);
					throw new IllegalArgumentException("This position cannot be shared with this element");
				}
			}
			if(!board.isValidPosition(position)) throw new IllegalPositionException("This position is not valid for this board");
			
			board.putElement(position, this);
		}
		
		this.board = board;
		this.position = position;
	}
	
	/**
	 * Checks if the Element's current board-position combination is valid.
	 * @return ...
	 * 		   | (getBoard() == null && getPosition() == null) || (getBoard() != null && getPosition() != null)
	 */
	public boolean hasProperBoardPosition(){
		return (getBoard() == null && getPosition() == null) || (getBoard() != null && getPosition() != null);
	}
	
	/**
	 * Invoked when an element is shot by a laser.
	 */
	public abstract void takeShot();
	
	/**
	 * Gives the hashcode of this element
	 * 
	 * @return ...
	 * 		   | if getPosition() != null
	 * 		   | then result >= getPosition().hashCode()
	 */
	@Override
	public int hashCode() {
		int result = 0;
		if(getBoard() != null && getPosition() != null) {
			result = getPosition().hashCode();
		}
		
		return result;
	}
	
	/**
	 * The element's board
	 */
	protected Board board;
	
	/**
	 * The element's position
	 */
	protected Position position;

	
}
